package api

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-admin-team/go-admin-core/config/source/file"
	"github.com/go-admin-team/go-admin-core/sdk"
	"github.com/go-admin-team/go-admin-core/sdk/api"
	"github.com/go-admin-team/go-admin-core/sdk/config"
	"github.com/go-admin-team/go-admin-core/sdk/pkg"
	"github.com/spf13/cobra"

	"go-admin/app/admin/models"
	"go-admin/app/admin/router"
	"go-admin/app/jobs"
	"go-admin/common/database"
	"go-admin/common/global"
	common "go-admin/common/middleware"
	"go-admin/common/middleware/handler"
	"go-admin/common/storage"
	ext "go-admin/config"
)

/*
*
这段代码是使用 Go 语言的 cobra 库来定义一个命令行接口（CLI）的示例。cobra 是一个流行的库，用于构建强大的现代 CLI 应用程序。下面是对这段代码的详细解释：

定义命令:
StartCmd 是一个指向 cobra.Command 结构体的指针，它代表了一个命令行命令。
Use: "server": 定义了命令的名称，即用户可以在命令行中输入 server 来调用这个命令。
命令描述:
Short: "Start API server": 为命令提供了一个简短的描述，通常用于帮助信息。
Example: "go-admin server -c config/settings.yml": 提供了一个命令的使用示例。
SilenceUsage: true: 当命令执行错误时，不输出用法信息。
PreRun 钩子:
PreRun 是一个在命令实际运行之前执行的函数。在这里，它调用了 setup() 函数（该函数在代码段中没有给出，但我们可以假设它执行了一些初始化或设置操作）。
RunE 函数:
RunE 是一个函数，当命令被调用时执行。与 Run 不同的是，RunE 返回一个错误，这使得你可以更清晰地处理可能的错误情况。
在这里，RunE 调用了 run() 函数（同样，该函数在代码段中没有给出），并返回其返回的错误（或 nil 如果没有错误）。我们可以假设 run() 函数是启动 API 服务器的主要逻辑，并且可能使用了 gin web 框架来创建和配置 web 服务器。
简而言之，这段代码定义了一个名为 server 的命令行命令，该命令在调用时会首先执行 setup() 函数进行初始化，然后执行 run() 函数来启动 API 服务器。如果 run() 函数返回错误，那么这个错误会被 cobra 捕获并可能显示给用户。
*/
var (
	configYml string
	apiCheck  bool
	//指针
	StartCmd = &cobra.Command{
		Use:          "server",
		Short:        "Start API server",
		Example:      "go-admin server -c config/settings.yml",
		SilenceUsage: true,
		PreRun: func(cmd *cobra.Command, args []string) {
			setup() //执行了下面的setup()方法
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			return run() //执行了下面的run()方法，run方法里面实例化了gin web框架
		},
	}
)

var AppRouters = make([]func(), 0)

// init() 函数：在 Go 中，init() 函数是特殊的。它会在包导入时自动执行，并且每个包可以包含多个 init() 函数（但通常只有一个）。这些函数用于初始化包的变量或执行其他设置任务。
/**
___go_build_go_admin.exe -- server -c config/settings.dev.yml运行顺序
func main() {
	cmd.Execute()
}
func init() 												->func InitRouter()

func setup()
func run()  ->func initRouter() ->common.InitMiddleware(r)
*/
func init() {
	//StartCmd.PersistentFlags()：这部分代码是为 StartCmd 这个 cobra.Command 结构体实例添加持久化标志（flags）。
	//StringVarP：定义了一个字符串类型的标志。这里定义了一个名为 "config" 的标志（短名称 "-c"），其默认值为 "config/settings.yml"。这个标志的用途是让用户提供一个配置文件路径来启动服务器。
	StartCmd.PersistentFlags().StringVarP(&configYml, "config", "c", "config/settings.yml", "Start server with provided configuration file")
	//BoolVarP：定义了一个布尔类型的标志。这里定义了一个名为 "api" 的标志（短名称 "-a"），其默认值为 false。这个标志的用途可能是让用户选择是否在启动服务器时检查 API 数据。
	//检查api,检查了所有api，有些费时间
	StartCmd.PersistentFlags().BoolVarP(&apiCheck, "api", "a", false, "Start server with check api data")

	//注册路由 fixme 其他应用的路由，在本目录新建文件放在init方法
	AppRouters = append(AppRouters, router.InitRouter)
}

// 注册全局日志监听
func setup() {
	// 注入配置扩展项
	config.ExtendConfig = &ext.ExtConfig
	//1. 读取配置
	config.Setup(
		file.NewSource(file.WithPath(configYml)),
		database.Setup,
		storage.Setup,
	)
	//注册监听函数
	queue := sdk.Runtime.GetMemoryQueue("")
	queue.Register(global.LoginLog, models.SaveLoginLog)
	queue.Register(global.OperateLog, models.SaveOperaLog)
	queue.Register(global.ApiCheck, models.SaveSysApi)
	go queue.Run() //启动了一个线程

	usageStr := `starting api server...`
	log.Println(usageStr)
}

func run() error {
	if config.ApplicationConfig.Mode == pkg.ModeProd.String() {
		gin.SetMode(gin.ReleaseMode)
	}
	initRouter()

	for _, f := range AppRouters {
		f()
	}

	srv := &http.Server{
		Addr:    fmt.Sprintf("%s:%d", config.ApplicationConfig.Host, config.ApplicationConfig.Port),
		Handler: sdk.Runtime.GetEngine(),
	}

	go func() {
		jobs.InitJob()
		jobs.Setup(sdk.Runtime.GetDb())

	}()

	if apiCheck {
		var routers = sdk.Runtime.GetRouter()
		q := sdk.Runtime.GetMemoryQueue("")
		mp := make(map[string]interface{})
		mp["List"] = routers
		message, err := sdk.Runtime.GetStreamMessage("", global.ApiCheck, mp)
		if err != nil {
			log.Printf("GetStreamMessage error, %s \n", err.Error())
			//日志报错错误，不中断请求
		} else {
			err = q.Append(message)
			if err != nil {
				log.Printf("Append message error, %s \n", err.Error())
			}
		}
	}

	go func() {
		// 服务连接
		if config.SslConfig.Enable {
			if err := srv.ListenAndServeTLS(config.SslConfig.Pem, config.SslConfig.KeyStr); err != nil && !errors.Is(err, http.ErrServerClosed) {
				log.Fatal("listen: ", err)
			}
		} else {
			if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
				log.Fatal("listen: ", err)
			}
		}
	}()
	fmt.Println(pkg.Red(string(global.LogoContent)))
	tip()
	fmt.Println(pkg.Green("Server run at:"))
	fmt.Printf("-  Local:   %s://localhost:%d/ \r\n", "http", config.ApplicationConfig.Port)
	fmt.Printf("-  Network: %s://%s:%d/ \r\n", "http", pkg.GetLocaHonst(), config.ApplicationConfig.Port)
	fmt.Println(pkg.Green("Swagger run at:"))
	fmt.Printf("-  Local:   http://localhost:%d/swagger/admin/index.html \r\n", config.ApplicationConfig.Port)
	fmt.Printf("-  Network: %s://%s:%d/swagger/admin/index.html \r\n", "http", pkg.GetLocaHonst(), config.ApplicationConfig.Port)
	fmt.Printf("%s Enter Control + C Shutdown Server \r\n", pkg.GetCurrentTimeStr())
	// 等待中断信号以优雅地关闭服务器（设置 5 秒的超时时间）
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)
	<-quit

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	fmt.Printf("%s Shutdown Server ... \r\n", pkg.GetCurrentTimeStr())

	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown:", err)
	}
	log.Println("Server exiting")

	return nil
}

//var Router runtime.Router

func tip() {
	usageStr := `欢迎使用 ` + pkg.Green(`go-admin `+global.Version) + ` 可以使用 ` + pkg.Red(`-h`) + ` 查看命令`
	fmt.Printf("%s \n\n", usageStr)
}

// 虽然名字教初始化router,但只是初始化了gin web这个框架，router还没有加进去
func initRouter() {
	var r *gin.Engine
	h := sdk.Runtime.GetEngine()
	if h == nil {
		h = gin.New()
		sdk.Runtime.SetEngine(h)
	}
	switch h.(type) {
	case *gin.Engine:
		r = h.(*gin.Engine)
	default:
		log.Fatal("not support other engine")
		//os.Exit(-1)
	}
	if config.SslConfig.Enable {
		r.Use(handler.TlsHandler())
	}
	//r.Use(middleware.Metrics())
	r.Use(common.Sentinel()).
		Use(common.RequestId(pkg.TrafficKey)).
		Use(api.SetRequestLogger)
	//初始化中间件
	common.InitMiddleware(r)

}
